﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Diagnostics;
using JdSoft.Apple.Apns.Notifications;
using JdSoft.Apple.Apns.Feedback;
using System.Threading;
using System.Web.Mvc;

namespace Presentation.Web.Admin
{
    public class PushNoteService
    {
        Domain.Uow.IRepository<Domain.Entity.PushContents> contentRepository;

        Domain.Uow.IRepository<Domain.Entity.MobileClient> clientRepository;

        Domain.Uow.IRepository<Domain.Entity.PushLog> pushLogRepository;

        public Domain.Entity.User PushUser { get; set; }

        public int SleepBetweenNote { get; set; }

        public int ConnNum { get; set; }

        public int SendRetry { get; set; }

        public int ReconnectDelay { get; set; }

        public string PushHostIp { get; set; }

        public int DataUpdate { get; set; }

        bool isSuccess = false;

        public PushNoteService(Domain.Uow.IRepository<Domain.Entity.MobileClient> client,
            Domain.Uow.IRepository<Domain.Entity.PushContents> content,
            Domain.Uow.IRepository<Domain.Entity.PushLog> log)
        {
            if (client == null)
            {
                throw new ArgumentNullException("clientRepository is null");
            }
            if (content == null)
            {
                throw new ArgumentNullException("contentRepository is null");
            }
            if (log == null)
            {
                throw new ArgumentNullException("pushLogRepository is null");
            }
            clientRepository = client;
            contentRepository = content;
            pushLogRepository = log;

            this.SleepBetweenNote = 5;
            this.ConnNum = 1;
            this.SendRetry = 1;
            this.ReconnectDelay = 1;
            this.PushHostIp = "127.0.0.1";
            this.DataUpdate = 1;
        }

        /// <summary>
        /// 用于pushcontroller中的 重新推送
        /// </summary>
        /// <param name="model"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public bool PushSingle(Domain.Entity.MobileClient model, Domain.Entity.PushContents content)
        {
            if (model != null && content != null)
            {
                NotificationService service = CreateNoteService();
                try
                {
                    Notification note = new Notification(model.DeviceToken);
                    note.Payload.AddCustom("contentId", content.Id);
                    note.Payload.AddCustom("clientId", model.Id);
                    note.Payload.AddCustom("time", System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                    note.Payload.Alert.Body = content.Content;
                    note.Payload.Sound = "default";
                    note.Payload.Badge = this.DataUpdate;
                    var sysLog = Infrastructure.Crosscutting.Comm.Service.LogCreateService.CreateLog();
                    if (service.QueueNotification(note))
                    {
                        sysLog.ShortMessage = "Notification Queued!";
                        sysLog.FullMessage = "Notification Queued! " + note.DeviceToken + " " + note.ToString();
                        this.AddAutoPushTextLog(sysLog.FullMessage);
                        Debug.WriteLine(sysLog.FullMessage);
                    }
                    else
                    {
                        sysLog.ShortMessage = "Notification Failed to be Queued!";
                        sysLog.FullMessage = "Notification Failed to be Queued! " + note.DeviceToken + " " + note.ToString();
                        this.AddAutoPushTextLog(sysLog.FullMessage);
                        Debug.WriteLine(sysLog.FullMessage);
                    }
                    Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(sysLog);
                }
                catch (Exception ex)
                {
                    var message = (ex.InnerException != null ? ex.InnerException.Message : ex.Message);
                    this.AddAutoPushTextLog(message);
                }
                finally
                {
                    if (service != null)
                    {
                        service.Close();
                        service.Dispose();
                    }
                }
            }
            return isSuccess;
        }

        /// <summary>
        /// 用于pushcontroller
        /// </summary>
        /// <param name="clientList"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public Dictionary<Domain.Entity.MobileClient, bool> PushListOneByOne(List<Domain.Entity.MobileClient> clientList, Domain.Entity.PushContents content)
        {
            var data = new Dictionary<Domain.Entity.MobileClient, bool>();
            if (clientList != null && clientList.Count > 0 && content != null)
            {
                int sleepBetweenNotifications = SleepBetweenNote * 1000;
                foreach (var item in clientList)
                {
                    isSuccess = false;
                    data.Add(item, PushSingle(item, content));

                    Debug.WriteLine("Sleeping " + sleepBetweenNotifications + " milliseconds before next Notification...");
                    this.AddAutoPushTextLog("Sleeping " + sleepBetweenNotifications + " milliseconds before next Notification...");
                    System.Threading.Thread.Sleep(sleepBetweenNotifications);
                }
            }
            return data;
        }

        /// <summary>
        /// 用于后台推送
        /// </summary>
        /// <param name="clientList"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public bool PushList(List<Domain.Entity.MobileClient> clientList, Domain.Entity.PushContents content)
        {
            var res = false;
            if (clientList != null && clientList.Count > 0)
            {
                NotificationService service = this.CreateNoteService();
                int sleepBetweenNotifications = SleepBetweenNote * 1000;
                try
                {
                    Notification note = null;
                    for (int i = 0; i < clientList.Count; i++)
                    {
                        if (!string.IsNullOrEmpty(clientList[i].DeviceToken) && clientList[i].DeviceToken.Length == 64)
                        {
                            note = new Notification(clientList[i].DeviceToken);
                            note.Payload.AddCustom("contentId", content.Id);
                            note.Payload.AddCustom("clientId", clientList[i].Id);
                            note.Payload.AddCustom("time", System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                            note.Payload.Alert.Body = content.Content;
                            note.Payload.Sound = "default";
                            note.Payload.Badge = this.DataUpdate;
                            var sysLog = Infrastructure.Crosscutting.Comm.Service.LogCreateService.CreateLog();
                            if (service.QueueNotification(note))
                            {
                                sysLog.ShortMessage = "Notification Queued!";
                                sysLog.FullMessage = "Notification Queued! " + note.DeviceToken + " " + note.ToString();
                                this.AddAutoPushTextLog(sysLog.FullMessage);
                                Debug.WriteLine(sysLog.FullMessage);
                            }
                            else
                            {
                                sysLog.ShortMessage = "Notification Failed to be Queued!";
                                sysLog.FullMessage = "Notification Failed to be Queued! " + note.DeviceToken + " " + note.ToString();
                                this.AddAutoPushTextLog(sysLog.FullMessage);
                                Debug.WriteLine(sysLog.FullMessage);
                            }
                            if (i < clientList.Count)
                            {
                                Debug.WriteLine("Sleeping " + sleepBetweenNotifications + " milliseconds before next Notification...");
                                this.AddAutoPushTextLog("Sleeping " + sleepBetweenNotifications + " milliseconds before next Notification...");
                                System.Threading.Thread.Sleep(sleepBetweenNotifications);
                            }
                            Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(sysLog);
                        }
                        else
                        {
                            continue;
                        }
                    }
                    this.AddAutoPushTextLog("Cleaning Up...");
                    Debug.WriteLine("Cleaning Up...");
                    res = true;
                }
                catch (Exception ex)
                {
                    var message = (ex.InnerException != null ? ex.InnerException.Message : ex.Message);
                    this.AddAutoPushTextLog(message);
                }
                finally
                {
                    service.Close();
                    service.Dispose();
                }
            }
            return res;
        }


        #region note conn

        protected void PushConnected(object sender)
        {
            this.AddAutoPushTextLog("Connected...");
            Debug.WriteLine("Connected...");
        }

        protected void PushConnecting(object sender)
        {
            this.AddAutoPushTextLog("Connecting...");
            Debug.WriteLine("Connecting...");
        }

        protected void PushDisconnected(object sender)
        {
            this.AddAutoPushTextLog("Disconnected...");
            Debug.WriteLine("Disconnected...");
        }

        #endregion

        #region note event

        protected void PushSuccess(object sender, Notification note)
        {
            var objContentId = note.Payload.CustomItems["contentId"].FirstOrDefault();
            var objClientId = note.Payload.CustomItems["clientId"].FirstOrDefault();
            var contentId = 0;
            var clientId = 0;
            if (objClientId != null)
            {
                int.TryParse(objClientId.ToString(), out clientId);
            }
            if (objContentId != null)
            {
                int.TryParse(objContentId.ToString(), out contentId);
            }
            var client = clientRepository.GetList().Where(e => e.DeviceToken == note.DeviceToken).FirstOrDefault();
            if (client != null && !client.State)
            {
                client.State = true;
                clientRepository.Modify(client);
                clientRepository.Uow.Commit();
            }
            this.AddPushLog(clientId, contentId, (int)Infrastructure.Crosscutting.Comm.Service.PushResultlType.Success);
            var message = "Notification Success: " + note.DeviceToken + " , " + note.ToString();
            this.AddAutoPushTextLog(message);
            Debug.WriteLine(message);
            isSuccess = true;
        }

        protected void PushFailed(object sender, Notification note)
        {
            var objContentId = note.Payload.CustomItems["contentId"].FirstOrDefault();
            var objClientId = note.Payload.CustomItems["clientId"].FirstOrDefault();
            var contentId = 0;
            var clientId = 0;
            if (objClientId != null)
            {
                int.TryParse(objClientId.ToString(), out clientId);
            }
            if (objContentId != null)
            {
                int.TryParse(objContentId.ToString(), out contentId);
            }
            this.AddPushLog(clientId, contentId, (int)Infrastructure.Crosscutting.Comm
                .Service.PushResultlType.Failed);
            var message = "Notification Failed: " + note.DeviceToken + " , " + note.ToString();
            this.AddAutoPushTextLog(message);
            Debug.WriteLine(message);
        }

        protected void PushError(object sender, Exception ex)
        {
            var error = Infrastructure.Crosscutting.Comm.Service.LogCreateService.CreateLog();
            error.FullMessage = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
            error.ShortMessage = "push error";
            Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(error);
            this.AddAutoPushTextLog(error.ShortMessage + " " + error.FullMessage);
            Debug.WriteLine(error.ShortMessage + " " + error.FullMessage);
        }

        protected void PushTooLong(object sender, NotificationLengthException ex)
        {
            if (ex.Notification != null && ex.Notification.Payload != null)
            {
                var item = ex.Notification.Payload.CustomItems;
                var objContentId = item["contentId"].FirstOrDefault();
                var objClientId = item["clientId"].FirstOrDefault();
                var contentId = 0;
                var clientId = 0;
                if (objClientId != null)
                {
                    int.TryParse(objClientId.ToString(), out clientId);
                }
                if (objContentId != null)
                {
                    int.TryParse(objContentId.ToString(), out contentId);
                }
                this.AddPushLog(clientId, contentId, (int)Infrastructure.Crosscutting.Comm
                    .Service.PushResultlType.TooLong);
            }
            var message = "Notification Too Long: " + ex.Notification.DeviceToken + " , " + ex.Notification.ToString();
            this.AddAutoPushTextLog(message);
            Debug.WriteLine(message);
        }

        protected void PushBadDeviceToken(object sender, BadDeviceTokenException ex)
        {
            //因为没有传过来clientid,还有内容Id，所以不能写到推送日志里面去
            var bad = Infrastructure.Crosscutting.Comm.Service.LogCreateService.CreateLog();
            bad.FullMessage = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
            bad.ShortMessage = "bad device " + ex.DeviceToken;
            Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(bad);
            //end 因为没有传过来clientid,还有内容Id，所以不能写到推送日志里面去

            this.AddAutoPushTextLog(bad.ShortMessage);
            Debug.WriteLine(bad.ShortMessage);
        }

        #endregion

        #region util

        public void AddAutoPushTextLog(string message)
        {
            if (PreAdminConfigSettingService.IsAddAutoPushTextLog)
            {
                Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(message);
            }
        }

        protected void AddPushLog(int clientId, int contentId, int result)
        {
            var log = new Domain.Entity.PushLog();
            log.AddTime = System.DateTime.Now;
            log.ClientId = clientId;
            log.ContentId = contentId;
            log.IpAddress = this.PushHostIp;
            log.Result = result;
            log.SortNum = 0;
            log.State = true;
            pushLogRepository.Add(log);
            pushLogRepository.Uow.Commit();
        }

        protected NotificationService CreateNoteService()
        {
            bool sandbox = PreAdminConfigSettingService.IsPushSandbox;


            string keyFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase
                , PreAdminConfigSettingService.PushKeyFilePath.Replace("~/", ""));

            var service = new NotificationService(sandbox, keyFilePath, PreAdminConfigSettingService.PushKeyFilePwd, ConnNum);
            service.SendRetries = SendRetry;
            service.ReconnectDelay = ReconnectDelay * 1000;
            service.Error += new NotificationService.OnError(PushError);
            service.NotificationTooLong += new NotificationService.OnNotificationTooLong(PushTooLong);
            service.BadDeviceToken += new NotificationService.OnBadDeviceToken(PushBadDeviceToken);
            service.NotificationFailed += new NotificationService.OnNotificationFailed(PushFailed);
            service.NotificationSuccess += new NotificationService.OnNotificationSuccess(PushSuccess);
            service.Connecting += new NotificationService.OnConnecting(PushConnecting);
            service.Connected += new NotificationService.OnConnected(PushConnected);
            service.Disconnected += new NotificationService.OnDisconnected(PushDisconnected);
            return service;
        }

        #endregion
    }

    public class TimerPushService
    {
        public TimerPushService()
        { }

        public void TimerPush(object stateInfo)
        {
            AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
            var message = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " Checking status " + autoEvent.Set() + ".";
            Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(message);
            Debug.WriteLine(message);
            //回收问题
            using (System.Net.WebClient client = new System.Net.WebClient())
            {
                try
                {
                    var url = "http://imap.ipow.cn/login/";
                    var res = client.DownloadString(url);
                    if (!string.IsNullOrEmpty(res) && res.Length > 100)
                    {
                        message = "auto timer push request login.......";
                        Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(message);
                    }
                }
                catch (Exception ex)
                {
                }
            }

            //是否启用定时推送
            if (PreAdminConfigSettingService.IsAutoPush)
            {
                var uow = new Infrastructure.Data.Uow.UnitOfWorkContext();
                var clientRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.MobileClient>(uow);
                var startLogRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.MobileClientStartLog>(uow);

                var autoPushRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.AutoPush>(uow);
                var autoPushdetailRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.AutoPushDetail>(uow);
                var autoPushTimerRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.AutoPushTimer>(uow);

                var contentRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.PushContents>(uow);

                var userRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.User>(uow);
                var pushLogRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.PushLog>(uow);
                var pushNoteService = new PushNoteService(clientRepository, contentRepository, pushLogRepository);
                pushNoteService.PushUser = userRepository.Get(1);

                //拿到要发送的token list
                var clientList = this.GetMobileClientTrueState(startLogRepository, clientRepository);
                //拿到内容
                var content = this.GetAutoPushContentTrueState(autoPushRepository, autoPushdetailRepository, autoPushTimerRepository, contentRepository);

                #region interval time push

                var now = System.DateTime.Now;
                foreach (var item in content)
                {
                    //计算设置的推送时间间隔
                    var temp = this.GetAutoPushTrueState(autoPushRepository).Where(d => d.ContentId == item.Id).FirstOrDefault();
                    if (temp != null)
                    {
                        //时间点推送情况时间间隔<=0,按过期时间推送
                        if (temp.IntervalTime <= 0 && now.Year == temp.ExpiredDateTime.Year
                            && now.Month == temp.ExpiredDateTime.Month && now.Day == temp.ExpiredDateTime.Day
                            && now.Hour == temp.ExpiredDateTime.Hour && now.Minute == temp.ExpiredDateTime.Minute)
                        {
                            pushNoteService.PushList(clientList, item);
                        }
                        //时间间隔>0,
                        else if (temp.IntervalTime > 0)
                        {
                            //时间段推送情况
                            var span = now - temp.AddTime;
                            var total = (int)span.TotalMinutes;
                            if (total > 0 && temp.IntervalTime > 0 && (total % temp.IntervalTime) == 0)
                            {
                                pushNoteService.PushList(clientList, item);
                            }
                        }
                    }
                }
                #endregion
            }
        }

        #region util

        protected List<Domain.Entity.MobileClient> GetMobileClientTrueState(
            Infrastructure.Data.Uow.EfRepository<Domain.Entity.MobileClientStartLog> startLogRepository,
            Infrastructure.Data.Uow.EfRepository<Domain.Entity.MobileClient> clientRepository)
        {
            var isSandbox = PreAdminConfigSettingService.IsPushSandbox;
            var keyFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase
                    , PreAdminConfigSettingService.PushKeyFilePath.Replace("~/", ""));
            var pwd = PreAdminConfigSettingService.PushKeyFilePwd;

            var fbService = new JdSoft.Apple.Apns.Feedback.FeedbackService(isSandbox, keyFilePath, pwd);
            fbService.Error += new FeedbackService.OnError(FeedbackError);
            fbService.Feedback += new FeedbackService.OnFeedback(FeedbackIndex);
            //从apns上拿到装过imap，但双删除了的token
            fbService.Run();
            fbService.Dispose();
            //end 从apns上拿到装过imap，但双删除了的token

            var expired = System.DateTime.Now.AddMonths(-6);
            //从启动日志里面找到近6个月有效的client id
            var clientIdList = startLogRepository.GetList().Where(e => e.AddTime > expired)
                .Select(e => e.ClientId).Distinct();
            //从Id里面找到 token
            var deviceList = clientRepository.GetList()
                .Where(e => clientIdList.Contains(e.Id))
                .Where(e => e.DeviceToken.Length == 64)
                .Where(e => e.State == true)
                .Distinct().ToList();
            return deviceList;
        }

        protected List<Domain.Entity.PushContents> GetAutoPushContentTrueState(
            Infrastructure.Data.Uow.EfRepository<Domain.Entity.AutoPush> autoPushRepository,
            Infrastructure.Data.Uow.EfRepository<Domain.Entity.AutoPushDetail> autoPushdetailRepository,
            Infrastructure.Data.Uow.EfRepository<Domain.Entity.AutoPushTimer> autoPushTimerRepository,
            Infrastructure.Data.Uow.EfRepository<Domain.Entity.PushContents> contentRepository)
        {
            var now = System.DateTime.Now;
            var hour = now.Hour;
            var minute = now.Minute;
            var data = (from auto in autoPushRepository.GetList()
                        join detail in autoPushdetailRepository.GetList() on auto.Id equals detail.AutoPushId
                        join timer in autoPushTimerRepository.GetList() on detail.TimerId equals timer.Id
                        join content in contentRepository.GetList() on auto.ContentId equals content.Id
                        where auto.State == true
                        && (
                            (
                            auto.ExpiredDateTime >= now &&
                            (
                            //9.30-12.30 9.40(在),9.12(不在)
                                (hour == timer.BeginHour && minute >= timer.BeninMinute) ||
                            //9.30-12.30 10.20(在),11.20(在)
                                (hour > timer.BeginHour && hour < timer.EndHour) ||
                            //9.30-12.30 12.30(在),12.40(不在)
                                (hour == timer.EndHour && minute <= timer.EndMinute))
                            ) || (
                            //精确到分就行了，
                            //auto.IntervalTime <= 0 && timer.BeginHour == hour && timer.BeninMinute == minute
                            auto.IntervalTime <= 0 &&
                            //年
                            auto.ExpiredDateTime.Year == now.Year &&
                            //月
                            auto.ExpiredDateTime.Month == now.Month &&
                            //日
                            auto.ExpiredDateTime.Day == now.Day &&
                            //时
                            auto.ExpiredDateTime.Hour == now.Hour &&
                            //分
                            auto.ExpiredDateTime.Minute == now.Minute
                            )
                        )
                        select content).Distinct();

            return data.ToList();
        }

        protected IQueryable<Domain.Entity.AutoPush> GetAutoPushTrueState(
            Infrastructure.Data.Uow.EfRepository<Domain.Entity.AutoPush> autoPushRepository)
        {
            var data = autoPushRepository.GetList().Where(e => e.State == true);
            return data;
        }

        #endregion

        #region feed back event

        protected void FeedbackIndex(object sender, Feedback feedback)
        {
            if (feedback != null && !string.IsNullOrEmpty(feedback.DeviceToken))
            {
                var uow = new Infrastructure.Data.Uow.UnitOfWorkContext();
                var clientRepository = new Infrastructure.Data.Uow.EfRepository<Domain.Entity.MobileClient>(uow);
                var client = clientRepository.GetList().Where(e => e.DeviceToken == feedback.DeviceToken).FirstOrDefault();
                if (client != null)
                {
                    client.State = false;
                    clientRepository.Modify(client);
                }
            }
            var message = "Feedback - Timestamp: " + feedback.Timestamp + " - DeviceId: " + feedback.DeviceToken;
            Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(message);
            Debug.WriteLine(message);
        }

        protected void FeedbackError(object sender, Exception ex)
        {
            var error = Infrastructure.Crosscutting.Comm.Service.LogCreateService.CreateLog();
            error.FullMessage = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
            error.ShortMessage = "feed back error";
            //添加到数据库中
            Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(error);
            //添加到文本日志中
            Infrastructure.Data.Uow.Log.LoggerRepositoryManager.AddLogInfo(error.ShortMessage + " " + error.FullMessage);
            Debug.WriteLine(error.ShortMessage + " " + error.FullMessage);
        }

        #endregion
    }

}